// HSS.Security.Principal.Impersonator.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       Impersonator.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Security.Principal
{
	#region Using directives.
	using System;
	using System.ComponentModel;
	using System.Runtime.InteropServices;
	using System.Security.Principal;
	#endregion

	/// <summary>
	/// Impersonation of a user. Allows code to execute under another
	/// users context.
	/// </summary>
	/// <remarks>
	/// Please note that the account that instantiates the Impersonator class
	/// needs to have the 'Act as part of operating system' privilege set in 
	/// order to impersonate another user regardless of downgrading or 
	/// elevating its access.
	/// 
	/// NOTE: Should only be used to downgrade access, meaning from an Adminstrator or System
	/// Account and impersonating a domain user that has a more restrictive access policy
	/// </remarks>
	public sealed class Impersonator : IDisposable
	{
		#region Login Type
		/// <summary>
		/// The Type of Logon for impersonating
		/// </summary>
		public enum LogonType : int
		{
			/// <summary>
			/// This logon type is intended for users who will be interactively using 
			/// the computer, such as a user being logged on by a terminal 
			/// server, remote shell, or similar process. This logon type has 
			/// the additional expense of caching logon information for disconnected 
			/// operations; therefore, it is inappropriate for some client/server 
			/// applications, such as a mail server.
			/// </summary>
			LOGON32_LOGON_INTERACTIVE = 2,
			/// <summary>
			/// This logon type is intended for high performance servers to authenticate 
			/// plaintext passwords. The LogonUser function does not cache credentials 
			/// for this logon type.
			/// </summary>
			LOGON32_LOGON_NETWORK = 3,
			/// <summary>
			/// This logon type is intended for batch servers, where processes may be 
			/// executing on behalf of a user without their direct intervention. This 
			/// type is also for higher performance servers that process many plaintext 
			/// authentication attempts at a time, such as mail or Web servers. The 
			/// LogonUser function does not cache credentials for this logon type.
			/// </summary>
			LOGON32_LOGON_BATCH = 4,
			/// <summary>
			/// Indicates a service-type logon. The account provided must have the 
			/// service privilege enabled.
			/// </summary>
			LOGON32_LOGON_SERVICE = 5,
			/// <summary>
			/// This logon type is for GINA DLLs that log on users who will be interactively 
			/// using the computer. This logon type can generate a unique audit record that 
			/// shows when the workstation was unlocked.
			/// </summary>
			LOGON32_LOGON_UNLOCK = 7,
			/// <summary>
			/// This logon type preserves the name and password in the authentication package, which 
			/// allows the server to make connections to other network servers while impersonating 
			/// the client. A server can accept plaintext credentials from a client, call 
			/// LogonUser, verify that the user can access the system across the network, and 
			/// still communicate with other servers. 
			/// 
			/// Windows NT:  This value is not supported.
			/// </summary>
			LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
			/// <summary>
			/// This logon type allows the caller to clone its current token and specify new 
			/// credentials for outbound connections. The new logon session has the same local 
			/// identifier but uses different credentials for other network connections. 
			/// 
			/// This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider. 
			/// 
			/// Windows NT:  This value is not supported.
			/// </summary>
			LOGON32_LOGON_NEW_CREDENTIALS = 9
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Initializes a new instance of the <see cref="Impersonator"/> class
		/// and begins the impersonation of the requested user account given the 
		/// credentials supplied.
		/// </summary>
		/// <param name="userName">The name of the user to act as</param>
		/// <param name="domainName">The domain name of the user to act as</param>
		/// <param name="password">The password of the user to act as</param>
		/// <param name="loginType">The type of logon action to take, recommend 
		/// <see cref="LogonType"/><c>.Interactive</c> in order to have access to most 
		/// activities otherwise use <see cref="LogonType"/><c>.Service</c>, 
		/// <see cref="LogonType"/><c>.Network</c> or <see cref="LogonType"/><c>.Batch</c>
		/// </param>
		/// <remarks>
		/// Please note that the account that instantiates the Impersonator class
		/// needs to have the 'Act as part of operating system' privilege set in 
		/// order to impersonate another user regardless of downgrading or 
		/// elevating its access.
		/// 
		/// NOTE: Should only be used to downgrade access, meaning from an Adminstrator or System
		/// Account and impersonating a domain user that has a more restrictive access policy
		/// </remarks>
		public Impersonator(string userName, string domainName, string password, LogonType loginType)
		{
			ImpersonateValidUser(userName, domainName, password, loginType);
		}
		#endregion

		#region IDisposable member
		/// <summary>
		/// Clean up resources
		/// </summary>
		public void Dispose()
		{
			UndoImpersonation();
		}
		#endregion

		#region P/Invoke

		[DllImport("advapi32.dll", SetLastError = true)]
		private static extern bool LogonUser(
			string lpszUserName,
			string lpszDomain,
			string lpszPassword,
			int dwLogonType,
			int dwLogonProvider,
			ref IntPtr phToken);

		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		private static extern int DuplicateToken(
			IntPtr hToken,
			int impersonationLevel,
			ref IntPtr hNewToken);

		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		private static extern int DuplicateTokenEx(
			IntPtr hToken,
			int dwDesiredAccess,
			int lpTokenAttributes,
			int impersonationLevel,
			int TokenType,
			ref IntPtr phNewToken);

		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		private static extern bool RevertToSelf();

		[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool CloseHandle(
			IntPtr handle);

		const int LOGON32_LOGON_INTERACTIVE = 2;
		const int LOGON32_LOGON_NETWORK = 3;
		const int LOGON32_LOGON_BATCH = 4;
		const int LOGON32_LOGON_SERVICE = 5;
		const int LOGON32_LOGON_UNLOCK = 7;
		const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
		const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

		private const int LOGON32_PROVIDER_DEFAULT = 0;

		#endregion

		#region Private member
		/// <summary>
		/// Does the actual impersonation.
		/// </summary>
		/// <param name="userName">The name of the user to act as</param>
		/// <param name="domain">The domain name of the user to act as</param>
		/// <param name="password">The password of the user to act as</param>
		/// <param name="logonType">The type of logon action to take</param>
		private void ImpersonateValidUser(string userName, string domain, string password, LogonType logonType)
		{
			WindowsIdentity tempWindowsIdentity = null;
			IntPtr token = IntPtr.Zero;

			try
			{
				if (LogonUser(userName, domain, password, (int)logonType, LOGON32_PROVIDER_DEFAULT, ref token))
				{
					tempWindowsIdentity = new WindowsIdentity(token);
					impersonationContext = tempWindowsIdentity.Impersonate();
				}
				else
				{
					throw new Win32Exception(Marshal.GetLastWin32Error());
				}
			}
			finally
			{
				if (token != IntPtr.Zero)
				{
					CloseHandle(token);
				}
			}
		}

		/// <summary>
		/// Reverts the impersonation.
		/// </summary>
		private void UndoImpersonation()
		{
			if (impersonationContext != null)
			{
				impersonationContext.Undo();
			}
		}

		private WindowsImpersonationContext impersonationContext = null;

		#endregion
	}
}